﻿using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using Launch.ViewModel;
using Serilog;
using ZCommon;
using ZDriver;

namespace Model;

public class UpdateDevProgram
{
    // 短响应超时时间,单位ms
    private const int ShortReplyTimeoutMs = 5000;

    // 长响应超时时间,单位ms
    private const int LongReplyTimeoutMs = 300000;

    // Channel读取线程运行标志,用于退出线程
    private readonly CancellationTokenSource _cts = new();

    // 一帧响应通道
    private readonly Channel<UpdateOneFrameResponse> _devUpdateFrameReplyChan =
        Channel.CreateUnbounded<UpdateOneFrameResponse>();

    // 长响应通道
    private readonly Channel<int> _devUpdateLongReplyChan = Channel.CreateUnbounded<int>();

    // 短响应通道
    private readonly Channel<int> _devUpdateShortReplyChan = Channel.CreateUnbounded<int>();

    // 串口驱动
    private readonly SerialPort_Driver _serialPortDriver = Singleton<SerialPort_Driver>.Instance();

    // 是否处于忙碌状态
    private volatile bool _isBusy;

    // 设备升级的VM
    private readonly DevUpdateVm _vm;

    /// <summary>
    ///     升级模型
    /// </summary>
    /// <param name="vm">设备升级的VM</param>
    public UpdateDevProgram(DevUpdateVm vm)
    {
        _vm = vm;

        // 连接parser事件,数据写入对应的通道
        var parser = Singleton<ProtocolParser>.Instance();
        // 连接短响应事件
        parser.DevUpdateShortReplyEvent += status => { _devUpdateShortReplyChan.Writer.WriteAsync(status); };
        // 连接长响应事件
        parser.DevUpdateLongReplyEvent += status => { _devUpdateLongReplyChan.Writer.WriteAsync(status); };
        // 连接一帧响应事件
        parser.DevUpdateFrameReplyEvent += response => { _devUpdateFrameReplyChan.Writer.WriteAsync(response); };
    }

    // public ChannelWriter<int> DevUpdateShortReplyChan => _devUpdateShortReplyChan.Writer;
    // public ChannelWriter<int> DevUpdateLongReplyChan => _devUpdateLongReplyChan.Writer;
    // public ChannelWriter<UpdateOneFrameResponse> DevUpdateFrameReplyChan => _devUpdateFrameReplyChan.Writer;

    /// <summary>
    ///     开始升级
    /// </summary>
    public void StartUpdate()
    {
        if (_isBusy)
        {
            Log.Warning("已经启动升级,请等待完成");
            return;
        }

        if (!_serialPortDriver.IsOpen())
        {
            Log.Error("串口未连接,无法升级");
            return;
        }

        // 置于忙碌状态
        _isBusy = true;

        // 执行升级任务
        Task.Run(async () =>
        {
            try
            {
                await DoUpdateAsync();
            }
            finally
            {
                _isBusy = false;
            }
        });
    }

    /// <summary>
    ///     异步升级
    /// </summary>
    private async Task DoUpdateAsync()
    {
        // 检查文件路径
        var filePath = _vm.UpdateFilePath;
        if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
        {
            Log.Error("无法升级,请选择正确的升级文件");
            _vm.UpdateStatus = "升级文件异常";
            return;
        }

        _vm.UpdateStatus = "开始升级";
        _vm.UpdateProgressPercentage = 0;

        // 一页的数据,Byte
        const int pageByteCount = 512;
        var pageData = new byte[pageByteCount];

        // 读取文件
        var fileData = await File.ReadAllBytesAsync(filePath);
        if (fileData.Length == 0)
        {
            Log.Error("升级文件为空,无法升级");
            _vm.UpdateStatus = "升级文件异常";
            return;
        }

        // 计算页数
        var pageCount = (fileData.Length + pageByteCount - 1) / pageByteCount;

        // 一帧的header
        var header = new ZPS_Pc();
        /*************** 首先发送1帧启动数据 **************/
        // 查看此事的节点ID
        var nodeId = _vm.SelectedDevice == "Local" ? (ushort) 0xFFFF : ushort.Parse(_vm.SelectedDevice);

        // 启动帧数据体
        var start = new UpdateStart
        {
            U16Id = nodeId,
            U32FrameMaxLength = pageByteCount,
            U32TotalLength = (uint) fileData.Length,
            U32TotalDataChecksum = ProtocolStructCommon.Checksum_Pure(fileData),
            U32TotalFrameCount = (uint) pageCount
        };
        // 启动帧的帧头
        header.U8Type = (byte) PSPCType.StartUpdateDev_Type;
        header.U32Length = (uint) Marshal.SizeOf<UpdateStart>();
        header.U16Id = nodeId;
        header.U32DataChecksum = ProtocolStructCommon.Checksum_Pure(ProtocolStructCommon.StructToByteArray(ref start));
        header.U16HeaderChecksum = (ushort) ProtocolStructCommon.Checksum_ZPS_PC(header);

        // 发送启动帧
        _serialPortDriver.Write(ProtocolStructCommon.StructToByteArray(ref header));
        _serialPortDriver.Write(ProtocolStructCommon.StructToByteArray(ref start));

        // Wait for device response
        try
        {
            var status =
                await _devUpdateShortReplyChan.Reader.ReadAsync(new CancellationTokenSource(ShortReplyTimeoutMs).Token);

            switch ((UpdateStatusEnum) status)
            {
                case UpdateStatusEnum.StartUpdateDev_Status:
                    Log.Information("开始启动升级,设备接收升级文件");
                    break;
                case UpdateStatusEnum.InvalidNodeId_Status:
                    Log.Error($"节点{nodeId}无法连接,无法启动升级");
                    return;
                default:
                    Log.Error($"设备异常,无法启动升级:{status}");
                    return;
            }
        }
        catch (OperationCanceledException)
        {
            _vm.UpdateStatus = "设备超时";
            Log.Error("设备超时,停止升级");
            return;
        }

        /*************** 开始循环发送帧数据,每帧都需要进行握手,接收应答的数据 **************/
        _vm.UpdateStatus = "设备接收文件";
        // 每帧数据
        header.U8Type = (byte) PSPCType.UpdateDevFrameSend_Type;

        // 文件数据流
        using var memoryStream = new MemoryStream(fileData);
        // 待发送的一帧数据
        var oneDataFrame = new UpdateOneFrame();

        for (var i = 0; i < pageCount; ++i)
        {
            // page index
            oneDataFrame.U32PageIdx = (uint) i;

            // 读取数据
            var size = await memoryStream.ReadAsync(pageData.AsMemory(0, pageByteCount));
            if (size == 0)
            {
                Log.Error("Data is empty, update stopped");
                _vm.UpdateStatus = "数据异常";
                return;
            }

            // 当前的一帧数据
            var tempPageData = pageData.AsSpan(0, size);

            header.U32Length = (uint) (Marshal.SizeOf<UpdateOneFrame>() + size);
            header.U32DataChecksum =
                ProtocolStructCommon.Checksum_Pure(ProtocolStructCommon.StructToByteArray(ref oneDataFrame)) +
                ProtocolStructCommon.Checksum_Pure(tempPageData);
            header.U16HeaderChecksum = (ushort) ProtocolStructCommon.Checksum_ZPS_PC(header);

            // 尝试发送帧数据
            var frameSentSuccessfully = false;
            var tryCount = 10;

            var curPageData = tempPageData.ToArray();
            while (tryCount-- > 0 && !frameSentSuccessfully)
                frameSentSuccessfully =
                    await SendFrameWithRetry(header, oneDataFrame, curPageData, i, pageCount);

            if (!frameSentSuccessfully)
            {
                Log.Error($"Failed to send frame {i}, update aborted");
                _vm.UpdateStatus = "无法发送数据";
                return;
            }
        }

        /*************** 等待板子回复 **************/
        while (true)
            try
            {
                var status =
                    await _devUpdateLongReplyChan.Reader.ReadAsync(
                        new CancellationTokenSource(LongReplyTimeoutMs).Token);

                switch ((UpdateStatusEnum) status)
                {
                    case UpdateStatusEnum.ReceiveDone_Status:
                        _vm.UpdateStatus = "数据接收完成,开始校验";
                        Log.Information(_vm.UpdateStatus);
                        break;
                    case UpdateStatusEnum.CheckDone_Status:
                        _vm.UpdateStatus = "校验完成,开始擦除Flash";
                        Log.Information(_vm.UpdateStatus);
                        break;
                    case UpdateStatusEnum.EraseFlashDone_Status:
                        _vm.UpdateStatus = "擦除Flash完成,开始写入Flash";
                        Log.Information(_vm.UpdateStatus);
                        break;
                    case UpdateStatusEnum.WriteFlashDone_Status:
                        _vm.UpdateStatus = "写入Flash完成,升级完成,请重新启动设备";
                        _vm.UpdateProgressPercentage = 100;
                        Log.Information(_vm.UpdateStatus);
                        return;
                    case UpdateStatusEnum.ReceiveError_Status:
                        _vm.UpdateStatus = "接收数据异常,退出升级";
                        Log.Error(_vm.UpdateStatus);
                        return;
                    case UpdateStatusEnum.CheckError_Status:
                        _vm.UpdateStatus = "数据校验错误,退出升级";
                        Log.Error(_vm.UpdateStatus);
                        return;
                    case UpdateStatusEnum.FlashError_Status:
                        _vm.UpdateStatus = "Flash操作错误,退出升级";
                        Log.Error(_vm.UpdateStatus);
                        return;
                    case UpdateStatusEnum.Unknown_Status:
                        _vm.UpdateStatus = "未知错误,退出升级";
                        Log.Error(_vm.UpdateStatus);
                        return;
                }
            }
            catch (OperationCanceledException)
            {
                Log.Error("等待超时,无法升级");
                return;
            }
    }

    /// <summary>
    ///     发送1帧数据,失败会等待重试
    /// </summary>
    /// <param name="header">一帧数据头</param>
    /// <param name="frame">数据体的结构体</param>
    /// <param name="pageData">数据体附属的一页数据</param>
    /// <param name="frameIndex">发送的帧id</param>
    /// <param name="totalFrames">总帧数</param>
    /// <returns></returns>
    private async Task<bool> SendFrameWithRetry(ZPS_Pc header, UpdateOneFrame frame, byte[] pageData,
        int frameIndex, int totalFrames)
    {
        _serialPortDriver.Write(ProtocolStructCommon.StructToByteArray(ref header));
        _serialPortDriver.Write(ProtocolStructCommon.StructToByteArray(ref frame));
        _serialPortDriver.Write(pageData);

        Log.Debug($"发送数据帧: {frameIndex}/{totalFrames - 1}");

        try
        {
            var response =
                await _devUpdateFrameReplyChan.Reader.ReadAsync(new CancellationTokenSource(ShortReplyTimeoutMs).Token);

            switch (response.U8Status)
            {
                case 0:
                    Log.Debug($"数据帧{response.U32FrameId} 正常");
                    _vm.UpdateProgressPercentage = (int) (frameIndex * 0.9 / totalFrames);
                    // 进入发送下一帧
                    return true;
                case 1:
                    Log.Error($"数据帧{response.U32FrameId} 错误!!!");
                    return false;
                default:
                    Log.Error("未知数据帧状态");
                    return false;
            }
        }
        catch (OperationCanceledException)
        {
            // 超时
            Log.Warning("设备接收帧超时");
            return false;
        }
    }
}